# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# (C) 2012 Remek Zajac
#!/usr/bin/python

import math

class Vector:
    def __init__(self, xy):
        try:
            self.x = xy[2]-xy[0]
            self.y = xy[3]-xy[1]
        except:
            try:
                self.x = xy[1][0]-xy[0][0]
                self.y = xy[1][1]-xy[0][1]
            except:
                self.x = xy[0]
                self.y = xy[1]

    def draw(self, image, origin, fill):
        dst = (origin[0]+self.x, origin[1]+self.y)
        draw = ImageDraw.Draw(image)
        draw.line((origin, dst), fill)

    def reverse(self):
        return Vector((-self.x,-self.y))

    def orthogonal(self):
        return Vector((-self.y, self.x))

    def setLength(self, length):
        if self.x == 0:
            return Vector((0, length))
        y2x = (1.0*self.y/self.x)
        newX = 1.0*math.copysign(1, self.x)*math.sqrt((length**2)/(1+y2x**2))
        newY = 1.0*math.copysign(1, self.y)*math.sqrt(length**2-newX**2)
        return Vector((newX, newY))

    def length(self):
        return math.sqrt(self.x**2 + self.y**2)

    def addToPoint(self, point):
        return (point[0]+self.x, point[1]+self.y)

class GeoRegion:
    nullNW = (-361,+361) #illegal nw (se extremum)
    nullSE = (361,-361)  #illegal sw (nw extremum)

    @staticmethod
    def _nw(latlon1, latlon2):
        if (latlon1 == GeoRegion.nullSE): #check illegal values, if latlon1 is nullNW, it will organically loose with latlon2, otherwise it might win, so prevent it
            return latlon2
        if (latlon2 == GeoRegion.nullSE):
            return latlon1
        return (max(latlon1[0], latlon2[0]), min(latlon1[1], latlon2[1]))

    @staticmethod
    def _se(latlon1, latlon2):
        if (latlon1 == GeoRegion.nullNW): #check illegal values, if latlon1 is nullSE, it will organically loose with latlon2, otherwise it might win, so prevent it
            return latlon2
        if (latlon2 == GeoRegion.nullNW):
            return latlon1
        return (min(latlon1[0], latlon2[0]), max(latlon1[1], latlon2[1]))
        
    def __init__(self, latlonRegion=(nullNW[0], nullNW[1], nullSE[0], nullSE[1])):
        self.m_nw = self._nw((latlonRegion[0], latlonRegion[1]), (latlonRegion[2], latlonRegion[3]))
        self.m_se = self._se((latlonRegion[0], latlonRegion[1]), (latlonRegion[2], latlonRegion[3]))
        
    #resize by given percent in all directions
    def percentResize(self, percent):
        halfNewWidthDelta = 1.0*self.width()*(percent-100)/200
        halfNewHeightDelta = 1.0*self.height()*(percent-100)/200
        nw = (self.m_nw[0] + halfNewHeightDelta, self.m_nw[1] - halfNewWidthDelta)
        se = (self.m_se[0] - halfNewHeightDelta, self.m_se[1] + halfNewWidthDelta)
        return GeoRegion((nw[0], nw[1], se[0], se[1]))

    #resise by the number of units (de/increment by n) in all directions
    def unitResize(self, units):
        halfNewWidthDelta = 1.0*units/2
        halfNewHeightDelta = 1.0*units/2
        nw = (self.m_nw[0] + halfNewHeightDelta, self.m_nw[1] - halfNewWidthDelta)
        se = (self.m_se[0] - halfNewHeightDelta, self.m_se[1] + halfNewWidthDelta)
        return GeoRegion((nw[0], nw[1], se[0], se[1]))

    def isNull(self):
        return self.m_nw == self.nullNW or self.m_se == self.nullSE
    
    def width(self):
        if not self.isNull():
            return math.fabs(self.m_nw[1]-self.m_se[1])
        return 0

    def height(self):
        if not self.isNull():        
            return math.fabs(self.m_nw[0]-self.m_se[0])
        return 0        

    def tuple(self):
        return (self.m_nw[0], self.m_nw[1], self.m_se[0], self.m_se[1])

    def mergeInto(self, geoRegionOrPoint):
        if isinstance(geoRegionOrPoint, GeoRegion):
            self.m_nw = self._nw(self.m_nw, geoRegionOrPoint.m_nw)
            self.m_se = self._se(self.m_se, geoRegionOrPoint.m_se)
        else:
            self.m_nw = self._nw(self.m_nw, geoRegionOrPoint)
            self.m_se = self._se(self.m_se, geoRegionOrPoint)

    def isNull(self):
        if self.m_nw == self.nullNW or self.m_se == self.nullSE:
            return True

    def contains(self, latlon):
        return ((latlon[0] < self.m_nw[0]) and (latlon[0] > self.m_se[0]) and
            (latlon[1] > self.m_nw[1]) and (latlon[1] < self.m_se[1]))

    def intersects(self, lineTuple):
        lineBegin = lineTuple[0]
        lineEnd = lineTuple[1]
        if ((lineBegin[0] > self.nw()[0] and lineEnd[0] > self.nw()[0]) or #line north of region
            (lineBegin[0] < self.se()[0] and lineEnd[0] < self.se()[0]) or #line south of region
            (lineBegin[1] < self.nw()[1] and lineEnd[1] < self.nw()[1]) or #line west of region
            (lineBegin[1] > self.se()[1] and lineEnd[1] > self.se()[1])):  #line east of region
            return False
        else:
            return True

    def centre(self):
        return (self.m_nw[0]-self.height()/2, self.m_nw[1]+self.width()/2)

    def nw(self):
        return self.m_nw

    def se(self):
        return self.m_se

